/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *            Copyright (C) 2018 Institute of Computing Technology, CAS
 *               Author : Han Shukai (email : hanshukai@ict.ac.cn)
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *         The kernel's entry, where most of the initialization work is done.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * */

#include <common.h>
#include <os/irq.h>
#include <os/mm.h>
#include <os/sched.h>
#include <screen.h>
#include <sbi.h>
#include <stdio.h>
#include <os/time.h>
#include <os/syscall.h>
#include <os/futex.h>
#include <os/queue.h>
#include <test.h>
#include <os/mm.h>
#include <csr.h>

extern void ret_from_exception();
extern void printk_task1(void);
extern void __global_pointer$();
queue_t ready_queue[3];
queue_t sleep_queue;
queue_t * current_queue=&ready_queue[0];
uint64_t init_kernel_sp=0x50500000+4096;
static void init_pcb()
{
  pcb[0].pid= process_id++;
  pcb[0].status = TASK_RUNNING;
  pcb[0].kernel_sp = init_kernel_sp;
  queue_init(&ready_queue[0]);
  queue_init(&ready_queue[1]);
  queue_init(&ready_queue[2]);
  int i=1;
  //init test_scheduler
	for(i=1;i<=num_sched2_tasks;i++){
    pcb[i].preempt_count=1;
		pcb[i].pid= process_id++;
   //stack
		pcb[i].kernel_sp = pcb[i-1].kernel_sp + PAGE_SIZE;
		pcb[i].user_sp = pcb[i].kernel_sp + PAGE_SIZE;
		//context
    for(int j=0;j<32;j++)
      pcb[i].kernel_context.regs[j]=0;
    pcb[i].kernel_context.regs[1]=sched2_tasks[i-1]->entry_point;
    pcb[i].kernel_context.regs[2]=pcb[i].kernel_sp;
    pcb[i].kernel_context.regs[3]=__global_pointer$;
    pcb[i].kernel_context.sstatus=34;
    pcb[i].kernel_context.sepc=sched2_tasks[i-1]->entry_point;
		//remember to init usercontext
   for(int j=0;j<32;j++)
      pcb[i].user_context.regs[j]=0;
    pcb[i].user_context.regs[1]=sched2_tasks[i-1]->entry_point;
    pcb[i].user_context.regs[2]=pcb[i].user_sp;
    pcb[i].user_context.regs[3]=__global_pointer$;
    pcb[i].user_context.sstatus=34;
    pcb[i].user_context.sepc=sched2_tasks[i-1]->entry_point;
		//ptr
		if(i>0)
			pcb[i].prev=&pcb[i-1];
		else
			pcb[i].prev=NULL;
		if(i+1 <= num_sched2_tasks+num_timer_tasks+num_lock_tasks)
			pcb[i].next=&pcb[i+1];
		else
			pcb[i].next=NULL;
		//type
		pcb[i].type=sched2_tasks[i-1]->type;
		pcb[i].status = TASK_READY;
		pcb[i].cursor_x=0;
		pcb[i].cursor_y=0;
    pcb[i].ready_queue=&ready_queue[0];
    queue_push(&ready_queue[0],(void *)&pcb[i]);
	}
 
 //init timer_test
   for(;i<=num_sched2_tasks+num_timer_tasks;i++){
    pcb[i].preempt_count=1;
		pcb[i].pid= process_id++;
   //stack
		pcb[i].kernel_sp = pcb[i-1].kernel_sp + PAGE_SIZE;
		pcb[i].user_sp = pcb[i].kernel_sp + PAGE_SIZE;
		//context
    for(int j=0;j<32;j++)
      pcb[i].kernel_context.regs[j]=0;
    pcb[i].kernel_context.regs[1] = timer_tasks[i-1-num_sched2_tasks]->entry_point;
    pcb[i].kernel_context.regs[2]=pcb[i].kernel_sp;
    pcb[i].kernel_context.regs[3]=__global_pointer$;
		pcb[i].kernel_context.sstatus=34;
    pcb[i].kernel_context.sepc=timer_tasks[i-1-num_sched2_tasks]->entry_point;
		//remember to init usercontext
   for(int j=0;j<32;j++)
      pcb[i].user_context.regs[j]=0;
    pcb[i].user_context.regs[1]=timer_tasks[i-1-num_sched2_tasks]->entry_point;
    pcb[i].user_context.regs[2]=pcb[i].user_sp;
    pcb[i].user_context.regs[3]=__global_pointer$;
    pcb[i].user_context.sstatus=34;
    pcb[i].user_context.sepc=timer_tasks[i-1-num_sched2_tasks]->entry_point;
		//ptr
		if(i>0)
			pcb[i].prev=&pcb[i-1];
		else
			pcb[i].prev=NULL;
		if(i+1 <= num_sched2_tasks+num_timer_tasks+num_lock_tasks)
			pcb[i].next=&pcb[i+1];
		else
			pcb[i].next=NULL;
		//type
		pcb[i].type=timer_tasks[i-1-num_sched2_tasks]->type;
		pcb[i].status = TASK_READY;
		pcb[i].cursor_x=0;
		pcb[i].cursor_y=0;
    pcb[i].ready_queue=&ready_queue[1];
    queue_push(&ready_queue[1],(void *)&pcb[i]);
	}
 
   //lock2_init
   
     for(;i<=num_sched2_tasks+num_timer_tasks+num_lock_tasks;i++){
     pcb[i].preempt_count=1;
		pcb[i].pid= process_id++;
   //stack
		pcb[i].kernel_sp = pcb[i-1].kernel_sp + PAGE_SIZE;
		pcb[i].user_sp = pcb[i].kernel_sp + PAGE_SIZE;
		//context
    for(int j=0;j<32;j++)
      pcb[i].kernel_context.regs[j]=0;
    pcb[i].kernel_context.regs[1] = lock2_tasks[i-1-num_sched2_tasks-num_timer_tasks]->entry_point;
    pcb[i].kernel_context.regs[2]=pcb[i].kernel_sp;
    pcb[i].kernel_context.regs[3]=__global_pointer$;
		pcb[i].kernel_context.sstatus=34;
    pcb[i].kernel_context.sepc=lock2_tasks[i-1-num_sched2_tasks-num_timer_tasks]->entry_point;
		//remember to init usercontext
   for(int j=0;j<32;j++)
      pcb[i].user_context.regs[j]=0;
    pcb[i].user_context.regs[1]=lock2_tasks[i-1-num_sched2_tasks-num_timer_tasks]->entry_point;
    pcb[i].user_context.regs[2]=pcb[i].user_sp;
    pcb[i].user_context.regs[3]=__global_pointer$;
    pcb[i].user_context.sstatus=34;
    pcb[i].user_context.sepc=lock2_tasks[i-1-num_sched2_tasks-num_timer_tasks]->entry_point;
		//ptr
		if(i>0)
			pcb[i].prev=&pcb[i-1];
		else
			pcb[i].prev=NULL;
		if(i+1 <= num_sched2_tasks+num_timer_tasks+num_lock_tasks)
			pcb[i].next=&pcb[i+1];
		else
			pcb[i].next=NULL;
		//type
		pcb[i].type=lock2_tasks[i-1-num_sched2_tasks-num_timer_tasks]->type;
		pcb[i].status = TASK_READY;
		pcb[i].cursor_x=0;
		pcb[i].cursor_y=0;
    pcb[i].ready_queue=&ready_queue[2];
    queue_push(&ready_queue[2],(void *)&pcb[i]);
	}
  current_running = &pcb[0];
  current_queue=&ready_queue[0];
}

static void init_syscall(void)
{
    // initialize system call table.
    int i;
	  for(i = 0; i < NUM_SYSCALLS; i++)
		  syscall[i] = (int (*)())0;
  	syscall[SYSCALL_SLEEP] = (int (*)())&do_sleep;
  	syscall[SYSCALL_BLOCK] = (int (*)())&do_block;
	  syscall[SYSCALL_UNBLOCK] = (int (*)())&do_unblock;
    syscall[SYSCALL_YIELD] = (int (*)())&do_scheduler;
  	syscall[SYSCALL_WRITE] = (int (*)())&screen_write;
  	syscall[SYSCALL_CURSOR] = (int (*)())&screen_move_cursor;
  	syscall[SYSCALL_REFLUSH] = (int (*)())&screen_reflush;
  	syscall[SYSCALL_MUTEX_LOCK_INIT] = (int (*)())&do_mutex_lock_init;
  	syscall[SYSCALL_MUTEX_LOCK_ACQUIRE] = (int (*)())&do_mutex_lock_acquire;
  	syscall[SYSCALL_MUTEX_LOCK_RELEASE] = (int (*)())&do_mutex_lock_release;
    syscall[SYSCALL_GET_TIMEBASE] = (int (*)())&get_time_base;
    syscall[SYSCALL_GET_TICK] = (int (*)())&get_ticks;
}

// jump from bootloader.
// The beginning of everything >_< ~~~~~~~~~~~~~~
int main()
{
    // init Process Control Block (-_-!)
    init_pcb();
    printk("> [INIT] PCB initialization succeeded.\n\r");

    // read CPU frequency
    time_base = sbi_read_fdt(TIMEBASE);
	
    // init futex mechanism
    init_system_futex();
    printk("> [INIT] Futex initialization succeeded.\n\r");
    // init interrupt (^_^)
    

    // init system call table (0_0)
    init_syscall();
    printk("> [INIT] System call initialized successfully.\n\r");

    // fdt_print(riscv_dtb);

    // init screen (QAQ)
    init_screen();
    printk("> [INIT] SCREEN initialization succeeded.\n\r");
    init_exception();
    printk("> [INIT] Interrupt processing initialization succeeded.\n\r");
    // TODO:
    // Setup timer interrupt and enable all interrupt

    while (1) {
        // (QAQQQQQQQQQQQ)
        // If you do non-preemptive scheduling, you need to use it
        // to surrender control do_scheduler();
        // enable_interrupt();
        // __asm__ __volatile__("wfi\n\r":::);
        //do_scheduler();
    };
    return 0;
}
